package com.gitee.wsl.collections.bag


import com.gitee.wsl.collections.bag.api.Bag
import com.gitee.wsl.ext.base.COLON
import com.gitee.wsl.ext.base.COMMA
import kotlin.jvm.Transient


/**
 * Abstract implementation of the [Bag] interface to simplify the creation
 * of subclass implementations.
 *
 *
 * Subclasses specify a Map implementation to use as the internal storage. The
 * map will be used to map bag elements to a number; the number represents the
 * number of occurrences of that element in the bag.
 *
 *
 * @param <E> the type of elements in this bag
 * @since 3.0 (previously DefaultMapBag v2.0)
</E> */
abstract class AbstractMapBag<E> : Bag<E> {
    /**
     * Inner class iterator for the Bag.
     */
    internal class BagIterator<E>(
        private val parent: AbstractMapBag<E>
    ) : MutableIterator<E> {
        private val entryIterator = parent.map.iterator()
        private var current: Map.Entry<E, MutableInteger>? = null
        private var itemCount = 0
        private val mods: Int = parent.modCount
        private var canRemove = false

        /** {@inheritDoc}  */
         override fun hasNext(): Boolean {
            return itemCount > 0 || entryIterator.hasNext()
        }

        /** {@inheritDoc}  */
         override fun next(): E {
            if (parent.modCount != mods) {
                throw ConcurrentModificationException()
            }
            if (itemCount == 0) {
                current = entryIterator.next()
                itemCount = current!!.value.value
            }
            canRemove = true
            itemCount--
            return current!!.key
        }

        /** {@inheritDoc}  */
         override fun remove() {
            if (parent.modCount != mods) {
                throw ConcurrentModificationException()
            }
            check(canRemove)
            val mut = current!!.value
            if (mut.value > 1) {
                mut.value--
            } else {
                entryIterator.remove()
            }
            parent.size--
            canRemove = false
        }
    }

    /**
     * Mutable integer class for storing the data.
     */
    protected class MutableInteger internal constructor(
        /** The value of this mutable.  */
        var value: Int,
    ) {
        override fun equals(other: Any?): Boolean {
            if (other !is MutableInteger) {
                return false
            }
            return other.value == value
        }

        override fun hashCode(): Int {
            return value
        }
    }

    /** The map to use to store the data  */
    @Transient
    private lateinit var map: MutableMap<E, MutableInteger>

    /** The current total size of the bag  */
    override var size = 0

    /** The modification count for fail fast iterators  */
    @Transient
    private var modCount = 0

    /** Unique view of the elements  */
    @Transient
    private var _uniqueSet: Set<E>? = null

    /**
     * Constructor needed for subclass serialisation.
     */
    protected constructor()

    /**
     * Constructor that assigns the specified Map as the backing store. The map
     * must be empty and non-null.
     *
     * @param map the map to assign
     */
    protected constructor(map: MutableMap<E, MutableInteger>) {
        this.map = map
    }

    /**
     * Constructs a new instance that assigns the specified Map as the backing store. The map
     * must be empty and non-null. The bag is filled from the iterable elements.
     *
     * @param map the map to assign.
     * @param iterable The bag is filled from these iterable elements.
     */
    protected constructor(map: MutableMap<E, MutableInteger>, iterable: Iterable<E>) : this(map) {
        iterable.forEach { `object`: E -> this.add(`object`) }
    }

    /**
     * Adds a new element to the bag, incrementing its count in the underlying map.
     *
     * @param element the object to add
     * @return `true` if the object was not already in the `uniqueSet`
     */
     override fun add(element: E): Boolean {
        return add(element, 1)
    }

    /**
     * Adds a new element to the bag, incrementing its count in the map.
     *
     * @param element the object to search for
     * @param nCopies the number of copies to add
     * @return `true` if the object was not already in the `uniqueSet`
     */
    override fun add(element: E, nCopies: Int): Boolean {
        modCount++
        if (nCopies > 0) {
            val mut = map[element]
            size += nCopies
            if (mut == null) {
                map.put(element, MutableInteger(nCopies))
                return true
            }
            mut.value += nCopies
        }
        return false
    }

    /**
     * Invokes [.add] for each element in the given collection.
     *
     * @param elements the collection to add
     * @return `true` if this call changed the bag
     */
     override fun addAll(elements: Collection< E>): Boolean {
        var changed = false
        for (current in elements) {
            val added = add(current)
            changed = changed || added
        }
        return changed
    }

    /**
     * Clears the bag by clearing the underlying map.
     */
     override fun clear() {
        modCount++
        map.clear()
        size = 0
    }

    /**
     * Determines if the bag contains the given element by checking if the
     * underlying map contains the element as a key.
     *
     * @param element the object to search for
     * @return true if the bag contains the given element
     */
    override fun contains(element: E): Boolean {
        return map.containsKey(element)
    }

    /**
     * Returns `true` if the bag contains all elements in the given
     * collection, respecting cardinality.
     *
     * @param other the bag to check against
     * @return `true` if the Bag contains all the collection
     */
    fun containsAll(other: Bag<E>): Boolean {
        for (current in other.uniqueSet()) {
            if (getCount(current) < other.getCount(current)) {
                return false
            }
        }
        return true
    }

    /**
     * Determines if the bag contains the given elements.
     *
     * @param elements the collection to check against
     * @return `true` if the Bag contains all the collection
     */
     override fun containsAll(elements: Collection<E>): Boolean {
        if (elements is Bag<*>) {
            return containsAll(elements as Bag<*>)
        }
        return containsAll(HashBag(elements))
    }

    /**
     * Read the map in using a custom routine.
     * @param map the map to use
     * @param in the input stream
     * @throws IOException any of the usual I/O related exceptions
     * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded
     * @throws ClassCastException if the stream does not contain the correct objects
     */
//    @Throws(IOException::class, java.lang.ClassNotFoundException::class)
//    protected fun doReadObject(map: Map<E?, MutableInteger?>, `in`: ObjectInputStream) {
//        this.map = map
//        val entrySize: Int = `in`.readInt()
//        for (i in 0..<entrySize) {
//            val obj = `in`.readObject() as E?
//            val count: Int = `in`.readInt()
//            map.put(obj, MutableInteger(count))
//            size += count
//        }
//    }

    /**
     * Write the map out using a custom routine.
     * @param out the output stream
     * @throws IOException any of the usual I/O related exceptions
     */
//    @Throws(IOException::class)
//    protected fun doWriteObject(out: ObjectOutputStream) {
//        out.writeInt(map.size())
//        for (entry in map.entrySet()) {
//            out.writeObject(entry.getKey())
//            out.writeInt(entry.getValue().value)
//        }
//    }

    /**
     * Compares this Bag to another. This Bag equals another Bag if it contains
     * the same number of occurrences of the same elements.
     *
     * @param other the Bag to compare to
     * @return true if equal
     */
    override fun equals(other: Any?): Boolean {
        if (other === this) {
            return true
        }
        if (other !is Bag<*>) {
            return false
        }
        val other = other as Bag<E>
        if (other.size != size) {
            return false
        }
        for (element in map.keys) {
            if (other.getCount(element) != getCount(element)) {
                return false
            }
        }
        return true
    }

    /**
     * Returns the number of occurrence of the given element in this bag by
     * looking up its count in the underlying map.
     *
     * @param element the object to search for
     * @return the number of occurrences of the object, zero if not found
     */
     override fun getCount(element: E): Int {
        val count = map.get(element)
        if (count != null) {
            return count.value
        }
        return 0
    }

    /**
     * Utility method for implementations to access the map that backs this bag.
     * Not intended for interactive use outside of subclasses.
     *
     * @return the map being used by the Bag
     */
//    protected fun getMap(): Map<E, MutableInteger> {
//        return map
//    }

    /**
     * Gets a hash code for the Bag compatible with the definition of equals.
     * The hash code is defined as the sum total of a hash code for each
     * element. The per element hash code is defined as
     * `(e==null ? 0 : e.hashCode()) ^ noOccurrences)`. This hash code
     * is compatible with the Set interface.
     *
     * @return the hash code of the Bag
     */
    override fun hashCode(): Int {
        var total = 0
        for (entry in map.entries) {
            val element= entry.key
            val count  = entry.value
            total += (element?.hashCode() ?: 0) xor count.value
        }
        return total
    }

    /**
     * Returns true if the underlying map is empty.
     *
     * @return true if bag is empty
     */
    override fun isEmpty(): Boolean = map.isEmpty()
    

    /**
     * Gets an iterator over the bag elements. Elements present in the Bag more
     * than once will be returned repeatedly.
     *
     * @return the iterator
     */
     override fun iterator(): MutableIterator<E> {
        return BagIterator(this)
    }

    /**
     * Removes all copies of the specified object from the bag.
     *
     * @param element the object to remove
     * @return true if the bag changed
     */
    override fun remove(element: E): Boolean {
        val mut = map[element]
        if (mut == null) {
            return false
        }
        modCount++
        map.remove(element)
        size -= mut.value
        return true
    }

    /**
     * Removes a specified number of copies of an object from the bag.
     *
     * @param element the object to remove
     * @param nCopies the number of copies to remove
     * @return true if the bag changed
     */
    override fun remove(element: E, nCopies: Int): Boolean {
        val mut = map[element]
        if (mut == null) {
            return false
        }
        if (nCopies <= 0) {
            return false
        }
        modCount++
        if (nCopies < mut.value) {
            mut.value -= nCopies
            size -= nCopies
        } else {
            map.remove(element)
            size -= mut.value
        }
        return true
    }

    /**
     * Removes objects from the bag according to their count in the specified
     * collection.
     *
     * @param elements the collection to use
     * @return true if the bag changed
     */
     override fun removeAll(elements: Collection<E>): Boolean {
        var result = false
        if (elements != null) {
            for (current in elements) {
                val changed = remove(current, 1)
                result = result || changed
            }
        }
        return result
    }

    /**
     * Remove any members of the bag that are not in the given bag, respecting
     * cardinality.
     * @see .retainAll
     * @param other the bag to retain
     * @return `true` if this call changed the collection
     */
    fun retainAll(other: Bag<E>): Boolean {
        var result = false
        val excess: Bag<E> = HashBag()
        for (current in uniqueSet()) {
            val myCount = getCount(current)
            val otherCount: Int = other.getCount(current)
            if (1 <= otherCount && otherCount <= myCount) {
                excess.add(current, myCount - otherCount)
            } else {
                excess.add(current, myCount)
            }
        }
        if (!excess.isEmpty()) {
            result = removeAll(excess)
        }
        return result
    }

    /**
     * Remove any members of the bag that are not in the given bag, respecting
     * cardinality.
     *
     * @param elements the collection to retain
     * @return true if this call changed the collection
     */
     override fun retainAll(elements: Collection<E>): Boolean {
        if (elements is Bag<*>) {
            return retainAll(elements as Bag<*>)
        }
        return retainAll(HashBag(elements))
    }

    /**
     * Returns an array of all of this bag's elements.
     *
     * @return an array of all of this bag's elements
     */
//     override fun toArray(): Array<Any?> {
//        val result = kotlin.arrayOfNulls<Any>(size())
//        var i = 0
//        for (current in map.keySet()) {
//            for (index in getCount(current) downTo 1) {
//                result[i++] = current
//            }
//        }
//        return result
//    }

    /**
     * Returns an array of all of this bag's elements.
     * If the input array has more elements than are in the bag,
     * trailing elements will be set to null.
     *
     * @param <T> the type of the array elements
     * @param array the array to populate
     * @return an array of all of this bag's elements
     * @throws ArrayStoreException if the runtime type of the specified array is not
     * a supertype of the runtime type of the elements in this list
     * @throws NullPointerException if the specified array is null
    </T> */
//     override fun <T> toArray(array: Array<T?>): Array<T?> {
//        var array = array
//        val size = size()
//        if (array.size < size) {
//            val unchecked = kotlin.reflect.jvm.internal.impl.load.kotlin.JvmType.Array.newInstance(
//                array.javaClass.getComponentType(),
//                size
//            ) as Array<T?>
//            array = unchecked
//        }
//
//        var i = 0
//        for (current in map.keySet()) {
//            for (index in getCount(current) downTo 1) {
//                // unsafe, will throw ArrayStoreException if types are not compatible, see Javadoc
//                val unchecked = current as T?
//                array[i++] = unchecked
//            }
//        }
//        while (i < array.size) {
//            array[i++] = null
//        }
//        return array
//    }

    /**
     * Implement a toString() method suitable for debugging.
     *
     * @return a debugging toString
     */
    override fun toString(): String {
        if (this.isEmpty()) {
            return "[]"
        }
        val buf = StringBuilder()
        buf.append("\"")
        val it = uniqueSet().iterator()
        while (it.hasNext()) {
            val current = it.next()
            val count = getCount(current)
            buf.append(count)
            buf.append(Char.COLON)
            buf.append(current)
            if (it.hasNext()) {
                buf.append(Char.COMMA)
            }
        }
        buf.append("\"")
        return buf.toString()
    }

    /**
     * Returns an unmodifiable view of the underlying map's key set.
     *
     * @return the set of unique elements in this bag
     */
     override fun uniqueSet(): Set<E> {
        if (_uniqueSet == null) {
            _uniqueSet = map.keys
        }
        return _uniqueSet!!
    }
    
}
